/*
 * SiFiSy - A File System Simulator
 * Copyright (C) 2010  SevenSoft
 *
 * This file is part of SiFiSy.
 *
 * SiFiSy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * SiFiSy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SiFiSy.  If not, see <http://www.gnu.org/licenses/>.
 */

package sifisy.core;

import java.util.Vector;
import org.jdom.*;

/**
 * NTFS class describes the type of file system NTFS
 * @author Daniele De Matteo
 * @version %I%, %G%
 * @createData
 */
public class Ntfs extends Simulation {
    private int baseRecord;//spazio indirizzamento nel record base
    private int extensionRecord;//spazio indirizzamento nel record estensione
    private int baseRecordCouples;
    private int extensionRecordCouples;
    private long recordsOccupied = 0;



    /**
     * Constructor
     */
    public Ntfs() {
        super();
    }

    /**
     * Method that returns the average of the inflactionIndex
     * @param
     * @return double
     */
    double inflactionIndexAverage() {
        double average = 0; /*Fyle System occupation*/
        double totFileSize = 0;
        for (int index = 0; index < files.size(); index++) {
            totFileSize = totFileSize + files.elementAt(index).getSize();
        }
        int sizeFS = fileSystemDimension();
        average = sizeFS / totFileSize;
        return average * 100;
    }

    /**
     * Method that calculates the filesystem dimension
     * @return int
     */
    int fileSystemDimension() {
        int sizeFS = 0; /*Fyle System occupation*/
        int blockSize = (int) (getBlockDimension() *
                Math.pow(2, getBlockExponent()));
        for (int index = 0; index < files.size(); index++) {
            sizeFS = sizeFS + getRecordsNumber(files.elementAt(index).getId()) *
                    blockSize;
        }
        return sizeFS;
    }

    /**
     * Method that calculates the simulation results when it's started:
     * maximum length file
     * average reading time
     * file system size
     * inflaction index
     * internal fragmentation
     */
    void start() {
        setSizeFileSystem(fileSystemDimension());
        super.setInflationIndex(inflactionIndexAverage());
        super.setMaxLenghtFile((long)(super.getPartitionDimension() *
                Math.pow(2,super.getPartitionExponent())));
        super.setInternalFragmentation(fragAverage());
        
        if(super.getChooseAdvanced())
            super.setAverageReading((int) readingTime());
    }

    /**
     * Method that return the number of records occupied by a file
     * @param id
     * @return int
     */
    int getRecordsNumber(String id) {
        int extRecordNumber = 0;
        int contiguosSequences = 0;
        int couple = getAddressDimension() * 2;

        //calcola il numero di sequenze di blocchi contigui:
        for (int index = 0; index < usedBlocks.size(); index++) {
            if (id.equals(usedBlocks.elementAt(index).getId())) {
                contiguosSequences = contiguosSequences + 1;
            }
        }

        //quante coppie stanno nel record base?
        int baseB = baseRecord - couple;//toglie la coppia{base, indice}
        //ogni sequenza contigua è denotata da una coppia {inizio, fine}, grande
        
        //la divisione intera fa il troncamento, mi va bene
        int baseSequences = baseB / couple;
        if (baseSequences > contiguosSequences) {
            recordsOccupied = recordsOccupied + 1;
            return 1;//il file sta tutto nel record base
        } else {
            contiguosSequences = contiguosSequences - baseSequences;
            //calcola quante coppie stanno in un record di estensione
            int extSequences = extensionRecord / couple;
            extRecordNumber = contiguosSequences / extSequences;
            if (contiguosSequences % extSequences != 0) {
                extRecordNumber = extRecordNumber + 1;
            }
            //calcola l'occupazione dei record di estensione
            //NTFS lascia 1/8 della dimensione della partizione per la MFT
            //int mft = getPartitionDimension() / 8;
            super.calculateAddressDimension();
            int extRecordSpace = extRecordNumber * super.getAddressDimension();
            int extensionRecordMem = extRecordSpace / extensionRecord;
            if (extRecordSpace % extensionRecord != 0) {
                extensionRecordMem = extensionRecordMem + 1;
            }
            recordsOccupied = recordsOccupied + extensionRecordMem +
                    extRecordNumber + 1;
            return extensionRecordMem + extRecordNumber + 1;
        }
    }

    /**
     * Function that returns an array of integer pairs, each pair describing
     * a run of the first file included (Beginning and length).
     * Set also the number of pairs contained in the base record
     * and those contained in the extension record
     * @return Vector<Integer>
     */
    public Vector<Integer> getFileRecordsComposition(){
        //conterrà il numeri di records da disegnare, quante coppie
        //disegnare in ogni indice poi coppie inizio - lunghezza
        Vector<Integer> recordsComposition = new Vector<Integer>();
        //deve dirmi come è fatto il primo blocco, (le coppie)
        //quanti altri blocchi ci sono, e come sono fatti.
        //prendi il primo file
        File first = super.getFiles().firstElement();
        //mi servono tutti i suoi pezzi
        Vector<Section> fileParts = new Vector<Section>();
        String fileId = first.getId();

        for (int index = 0; index < usedBlocks.size(); index++)
            if (fileId.equals(usedBlocks.elementAt(index).getId()))
                fileParts.add(usedBlocks.elementAt(index));

        //fileParts contiene tutte le parti in cui è diviso il file
        //quante coppie stanno nel record base?
        int couple = getAddressDimension() * 2;
        
        int baseB = baseRecord - couple;//toglie la coppia{base, indice}
        //ogni sequenza contigua è denotata da una coppia {inizio, fine}, grande

        //la divisione intera fa il troncamento, mi va bene
        int baseSequences = baseB / couple;
        if (baseSequences > fileParts.size()){//c'è solo un record, con una
            //unica coppia
            setBaseRecordCouples(1);
        }else{
            //ci sono più records da disegnare, inserisco quanti ne deve disegnare
            //quante coppie disegnare nel record base, quante disegnarne nei record
            //d'estensione.
            //recordsComposition.add(getRecordsNumber(fileId));
            setBaseRecordCouples(baseSequences);
            int extSequences = extensionRecord / couple;
            setExtensionRecordCouples(extSequences);
        }

        for(int i = 0; i < fileParts.size(); i++){
            recordsComposition.add((int)fileParts.elementAt(i).getFirst());
            recordsComposition.add((int)fileParts.elementAt(i).getLenght());
        }
        //recordsComposition contiene tutte le coppie che indirizzano il file

        return recordsComposition;
    }

/**
     * Open an existing NTFS simulation
     * @param simulationRoot
     */
    @Override
    void open(Element simulationRoot) {
        Element fileSystemRoot = simulationRoot.getChild("fileSystem");
        baseRecord = Integer.parseInt((fileSystemRoot.
                getChild("baseRecord")).getText());
        extensionRecord = Integer.parseInt((fileSystemRoot.
                getChild("extensionRecord")).getText());
        super.open(simulationRoot);
    }

    /**
     * Save an NTFS simulation
     * @param simulationRoot
     */
    @Override
    void save(Element simulationRoot) {
        super.save(simulationRoot);
        simulationRoot.addContent("\n    ");
        Element FileSystem = new Element("fileSystem");
        FileSystem.addContent("\n        ");
        FileSystem.addContent(new Element("type").addContent("NTFS"));
        FileSystem.addContent("\n        ");
        FileSystem.addContent(new Element("baseRecord").
                addContent(String.valueOf(baseRecord)));
        FileSystem.addContent("\n        ");
        FileSystem.addContent(new Element("extensionRecord").
                addContent(String.valueOf(extensionRecord)));
        FileSystem.addContent("\n    ");
        simulationRoot.addContent(FileSystem);
        simulationRoot.addContent("\n");
    }

    /**
     * Method that return the value of parameter BaseRecord
     * @return
     */
    int getBaseRecord() {
        return baseRecord;
    }

    /**
     * Method that return the value of parameter ExtensionRecord
     * @return
     */
    int getExtensionRecord() {
        return extensionRecord;
    }

    /**
     * Method that set the value of parameter BaseRecord
     * @param baseRecord
     */
    void setBaseRecord(int baseRecord) {
        this.baseRecord = baseRecord;
    }

    /**
     * Method that set the value of parameter ExtensionRecord
     * @param baseRecord
     */
    void setExtensionRecord(int extensionRecord) {
        this.extensionRecord = extensionRecord;
    }

    /**
     * Method that calculate the size of max file inserted in hard disk
     */
    void viewMaxFile(){
         super.calculateAddressDimension();
         super.setMaxLenghtFile((long)
                (super.getPartitionDimension()*
                Math.pow(2, super.getPartitionExponent())));
    }

    /**
     * Method that return the value of parameter BaseRecordCouples
     * @return
     */
    public int getBaseRecordCouples() {
        return baseRecordCouples;
    }

    /**
     * Method that set the value of parameter BaseRecordCouples
     * @param baseRecordCouples
     */
    public void setBaseRecordCouples(int baseRecordCouples) {
        this.baseRecordCouples = baseRecordCouples;
    }

    /**
     * Method that return the value of parameter ExtensionRecordCouples
     * @return
     */
    public int getExtensionRecordCouples() {
        return extensionRecordCouples;
    }

    /**
     * Method that set the value of parameter ExtensionRecordCouples
     * @param extensionRecordCouples
     */
    public void setExtensionRecordCouples(int extensionRecordCouples) {
        this.extensionRecordCouples = extensionRecordCouples;
    }

    /**
     *
     * @return
     */
    public long getRecordsOccupied() {
        return recordsOccupied;
    }

    /**
     * 
     * @param recordsOccupied
     */
    public void setRecordsOccupied(long recordsOccupied) {
        this.recordsOccupied = recordsOccupied;
    }

    /**
     * Method that set configuration settigs
     * @return vector with configuration settings
     */
    @Override
    public Vector<String[]> getConfigurationValues() {
        Vector<String[]> config = super.getConfigurationValues();

        config.insertElementAt(new String[]{"File System", "NTFS"}, 0);
        config.add(new String[]{Model.translate("mainRecord"),
                    String.valueOf(baseRecord) + " Byte"});
        config.add(new String[]{Model.translate("extensionRecord"),
                    String.valueOf(extensionRecord) + " Byte"});

        return config;
    }

    /**
     * Method that customize the result of Ext2 simulation
     * @return a Vector with String results
     */
    public Vector<String[]> getResultsValues(){
        Vector<String[]> results = new Vector<String[]>();

        results.add(new String[]{Model.translate("maxFile"),
                    String.valueOf(getMaxLenghtFile() / 1024) + " KB"});
        results.add(new String[]{Model.translate("internalFragm"),
                    String.valueOf(getInternalFragmentation())});
        results.add(new String[]{Model.translate("inflactionIndex"),
                    String.valueOf(getInflationIndex()) + " %"});
        results.add(new String[]{Model.translate("fileSystemSize"),
                    String.valueOf(super.getSizeFileSystem() / 1024) + " KB"});
        if(files.size() == 1)
            results.add(new String[]{Model.translate("recordsOccupied"),
                    String.valueOf(getRecordsOccupied())});

        if (getChooseAdvanced())
            results.add(new String[]{Model.translate("averageReading"),
                        String.valueOf(getAverageReading()) + " ms"});

        return results;
    }
}